package com.freesky.system.task;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.freesky.common.config.ProjectConfig;
import com.freesky.common.constant.RedisLockConstant;
import com.freesky.common.core.domain.AjaxResult;
import com.freesky.common.core.domain.model.LoginUser;
import com.freesky.common.core.redis.RedisLockUtil;
import com.freesky.common.enums.ExportStatusEnum;
import com.freesky.common.exception.CustomException;
import com.freesky.common.utils.SecurityUtils;
import com.freesky.common.utils.poi.ExcelUtil;
import com.freesky.common.utils.spring.SpringUtils;
import com.freesky.system.domain.TExportRecord;
import com.freesky.system.service.ITExportRecordService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.io.File;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * @author lwl
 * @create 2022/5/21 18:06
 */
@Component("SyncReport")
public class SyncReport {

    private static final Logger log = LoggerFactory.getLogger(SyncReport.class);

    private Integer pageSize = 50000;
    private Integer pageSheet = 10;
    /**
     * Redis分布式锁
     */
    @Autowired
    private RedisLockUtil redisLockUtil;

    @Autowired
    private ITExportRecordService exportRecordService;

    @Autowired
    private DelegatingSecurityContextAsyncTaskExecutor delegatingSecurityContextAsyncTaskExecutor;


    /**
     * 多级表头
     */
    @Async
    public AjaxResult exportFileMutilHeader(Object entity, Object vo, Class serviceClass, String methodName, String sheetName, Long exportRecordId, String userName) {
        AjaxResult result = AjaxResult.error();
        //获取导入记录锁按类名方法名登入用户名
        final String LOCK_NAME = RedisLockConstant.getLockName(RedisLockConstant.EXPORT_USER_LOCK, serviceClass.getName() + ":" + methodName + ":" + userName);
        //给当前导入操作上锁。true，上锁成功；false，上锁失败。
        boolean locked = this.redisLockUtil.lock(LOCK_NAME, RedisLockConstant.LOCK_TIME);
        //若上锁失败
        if (!locked) {
            throw new CustomException("当前存在正在导入的请求，请稍后再试");
        }

        TExportRecord record = new TExportRecord();
        String fileName = encodingFilename(sheetName);
        ExcelWriter excelWriter = EasyExcel.write(getAbsoluteFile(fileName), vo.getClass()).build();
        //WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build();
        try {
            record = exportRecordService.selectTExportRecordById(exportRecordId);
            record.setStatus(ExportStatusEnum.SUBMIT_FAIL.getValue());

            Method declaredMethod = serviceClass.getDeclaredMethod(methodName, entity.getClass());
            Class<?> returnType = declaredMethod.getReturnType();
            if (List.class != returnType) {
                throw new CustomException("目前仅支持返回为list的方法");
            }
            Object service = SpringUtils.getBean(serviceClass);
            Page<Object> page = PageHelper.startPage(1, 10, true);
            ReflectionUtils.invokeMethod(declaredMethod, service, entity);
            long total = page.getTotal();
            if (total != 0) {
                long totalPageNum = total / pageSize + 1;
                long totalSheet = totalPageNum / pageSheet + 1;
                List<WriteSheet> writeSheetList = new ArrayList<>();
                for(int sheetNum=0;sheetNum<totalSheet;sheetNum++){
                    WriteSheet writeSheet = EasyExcel.writerSheet(sheetName + String.valueOf(sheetNum+1)).build();
                    writeSheetList.add(writeSheet);
                }
                for (int pageNum = 1; pageNum <= totalPageNum; pageNum++) {
                    int finalPageNum = pageNum;
                    PageHelper.startPage(pageNum, pageSize, false);
                    Object o = ReflectionUtils.invokeMethod(declaredMethod, service, entity);
                    int curSheetNum = (finalPageNum-1) / pageSheet;
                    excelWriter.write((List) o,writeSheetList.get(curSheetNum));
                    //excelWriter.write((List) o, writeSheet);
                }
            }
            /*
            if (total != 0) {
                long totalPageNum = total / pageSize + 1;
                ConcurrentHashMap<Integer, List> map = new ConcurrentHashMap(16);
                CountDownLatch countDownLatch = new CountDownLatch((int) totalPageNum);
                for (int pageNum = 1; pageNum <= totalPageNum; pageNum++) {
                    int finalPageNum = pageNum;
                    Future<?> submit = delegatingSecurityContextAsyncTaskExecutor.submit(() -> {
                        PageHelper.startPage(finalPageNum, pageSize, false);
                        Object o = ReflectionUtils.invokeMethod(declaredMethod, service, entity);
                        map.put(finalPageNum, (List) o);
                        countDownLatch.countDown();
                    });
                }
                countDownLatch.await();

                for (int pageNum = 1; pageNum <= totalPageNum; pageNum++) {
                    List list1 = map.get(pageNum);
                    if(list1!=null && list1.size()>0) {
                        excelWriter.write(list1, writeSheet);
                    }
                }
            }*/

            record.setFileName(fileName);
            record.setStatus(ExportStatusEnum.SUBMIT_SUCCESS.getValue());
            record.setFileUrl(ProjectConfig.getDownloadPath() + fileName);

        } catch (Exception e) {
            if (e instanceof CustomException) {
                throw (CustomException) e;
            }
            log.error("=======解析Excel时发生错误", e);
            throw new CustomException("解析Excel时发生错误", e);
        } finally {
            exportRecordService.update(record);
            //更新数据库记录的状态和下载地址
            // 若已锁住，则解锁
            if (locked) {
                this.redisLockUtil.unLock(LOCK_NAME);
            }
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
        return result;
    }

    /*
    @Async
    public AjaxResult easyExcelexportFile(Object entity, Class serviceClass, String methodName, String sheetName, Long exportRecordId) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (null == loginUser){
            throw new CustomException("获取登入信息失败");
        }
        AjaxResult result = AjaxResult.error();
        //获取导入记录锁按类名方法名登入用户名
        final String LOCK_NAME = RedisLockConstant.getLockName(RedisLockConstant.EXPORT_USER_LOCK, serviceClass.getName()+":"+methodName+":"+loginUser.getUser().getUserName());
        //给当前导入操作上锁。true，上锁成功；false，上锁失败。
        boolean locked = this.redisLockUtil.lock(LOCK_NAME, RedisLockConstant.LOCK_TIME);
        //若上锁失败
        if (!locked) {
            throw new CustomException("当前存在正在导入的请求，请稍后再试");
        }
        TExportRecord record = exportRecordService.selectTExportRecordById(exportRecordId);
        record.setStatus(ExportStatusEnum.SUBMIT_FAIL.getValue());
        try {
            //新建一条下载记录，状态是正在下载，下载地址是空的
            Method declaredMethod = serviceClass.getDeclaredMethod(methodName, entity.getClass());
            Class<?> returnType = declaredMethod.getReturnType();
            if(List.class!=returnType){
                throw new CustomException("目前仅支持返回为list的方法");
            }
            Object service = SpringUtils.getBean(serviceClass);
            Page<Object> page = PageHelper.startPage(1, -1, true);
            ReflectionUtils.invokeMethod(declaredMethod,service,entity);
            long total = page.getTotal();

            List list = Collections.synchronizedList(new ArrayList<>());
            if (total >0 ) {
                long totalPageNum = total / pageSize + 1;
                ConcurrentHashMap<Integer, List> map = new ConcurrentHashMap(16);
                CountDownLatch countDownLatch = new CountDownLatch((int) totalPageNum);
                for (int pageNum = 1; pageNum <= totalPageNum; pageNum++) {
                    int finalPageNum = pageNum;
                    delegatingSecurityContextAsyncTaskExecutor.execute(new Runnable() {
                        @Override
                        public void run() {
                            PageHelper.startPage(finalPageNum, pageSize, false);
                            Object o = ReflectionUtils.invokeMethod(declaredMethod, service, entity);
                            map.put(finalPageNum, (List) o);
                            countDownLatch.countDown();
                        }
                    });
                }
                countDownLatch.await();
                for (int pageNum = 1; pageNum <= totalPageNum; pageNum++) {
                    List list1 = map.get(pageNum);
                    if (list != null) {
                        list.addAll(list1);
                    }
                }
            }
            ExcelUtil util = new ExcelUtil(entity.getClass());
            result = util.exportExcel(list, sheetName);
            if (result.isSuccess()) {
                String fileName = result.getMsg();
                record.setFileName(fileName);
                record.setStatus(ExportStatusEnum.SUBMIT_SUCCESS.getValue());
                record.setFileUrl(ProjectConfig.getDownloadPath() + fileName);
                log.info(util.getAbsoluteFile(fileName));
            }
        }catch (Exception e) {
            if (e instanceof CustomException) {
                throw (CustomException) e;
            }
            log.error("=======解析Excel时发生错误", e);
            throw new CustomException("解析Excel时发生错误", e);
        } finally {
            //更新数据库记录的状态和下载地址
            try {
                exportRecordService.update(record);
            }catch (Exception e){
                log.error("=======导出记录记录失败", e);
            }
            // 若已锁住，则解锁
            if (locked) {
                this.redisLockUtil.unLock(LOCK_NAME);
            }
        }
        return result;
    }*/


    public <T> int batchSave(List<T> data, Page.Function<List<T>, Integer> daoFunc) throws ExecutionException, InterruptedException {
        Future<Integer> submit = delegatingSecurityContextAsyncTaskExecutor.submit(() -> daoFunc.apply(data));

        if (Objects.compare(submit.get(), 0, Comparator.naturalOrder()) <= 0) {
            return 0;
        }
        return submit.get();
    }


    /**
     * 编码文件名
     */
    public String encodingFilename(String filename) {
        filename = UUID.randomUUID().toString() + "_" + filename + ".xlsx";
        return filename;
    }

    /**
     * 获取下载路径
     *
     * @param filename 文件名称
     */
    public String getAbsoluteFile(String filename) {
        String downloadPath = ProjectConfig.getDownloadPath() + filename;
        File desc = new File(downloadPath);
        if (!desc.getParentFile().exists()) {
            desc.getParentFile().mkdirs();
        }
        return downloadPath;
    }

}
